/* gearserial.c -- Arquivo da bilbioteca para enviar comandos pela
 * serial
 *
 * Copyright (C) 2007, Diogo F. S. Ramos <diogofsr at gmail.com>
 * GEAR - Grupo de Estudos Avançados em Robótica
 * <http://www.sel.eesc.usp.br/gear>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <termios.h>

#include "xmalloc.h"
#include "xerror.h"
#include "gearserial.h"

GearSerial*
gearserial_new( void )
{
    GearSerial *self = XMALLOC(GearSerial, 1);

    self->end_serial = NULL;
    self->sleep_time = 0;
    self->byte = 0x00;
    self->many_times = 1;
    
    return self;
}

int
gearserial_open_serial( GearSerial *self,
			const char *path )
{
    self->end_serial = XMALLOC( char, strlen(path) + 1 );
    strcpy( self->end_serial, path );
    self->fd = open(self->end_serial, O_WRONLY | O_NOCTTY | O_NDELAY);
    if( self->fd == -1 )
    {
	xerror_error("Nao consegui abrir a serial");
	xerror_error(path);
	return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

int
gearserial_set_raw_mode( GearSerial * self )
{
    struct termios term;

    tcgetattr( self->fd, &term );
    cfmakeraw(&term);


    term.c_iflag &= ~ ( BRKINT | ICRNL | IGNCR | INLCR | IGNPAR | INPCK |
			PARMRK | ISTRIP | IXOFF | IXON | IMAXBEL);
    term.c_iflag |= IGNBRK;

    /* output mode flags */
    term.c_oflag &= ~ ( OPOST | ONLCR | OCRNL | ONOCR | ONLRET | OFILL |
			OFDEL | NLDLY | CRDLY | TABDLY | BSDLY | VTDLY |
			FFDLY);
    
    /* local mode flags */
    term.c_lflag &= ~ ( ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN |
			ISIG | TOSTOP );
    term.c_lflag |= NOFLSH;
    
    /* control mode flags */
    term.c_cflag &= ~ ( HUPCL | CSTOPB | PARENB | CSIZE);
    term.c_cflag |= CLOCAL | CREAD | CS8;
    
    /* set terminal attributes */
    if (tcsetattr(self->fd, TCSANOW, &term) != 0)
	return 2;

    gearserial_set_baudrate( self, B9600 );
    
    return EXIT_SUCCESS;
}

int
gearserial_set_baudrate( GearSerial *self,
			 int baudrate )
{
    struct termios term;
    
    /* get current settings */
    if (tcgetattr(self->fd, & term) != 0)
    {
	xerror_error("Nao pude pegar os atributos do termios");
	return EXIT_FAILURE;
    }
    
    /* set speed */
    if (cfsetispeed (&term, baudrate) != 0)
    {
	xerror_error("Nao pude setar a velocidade de entrada");
	return EXIT_FAILURE;
    }
    if (cfsetospeed (&term, baudrate) != 0)
    {
	xerror_error("Nao pude setar a velocidade de saida");
	return EXIT_FAILURE;
    }
    
    /* set terminal attributes */
    if (tcsetattr(self->fd, TCSANOW, &term) != 0)
    {
	xerror_error("Nao pude setar os atributos do termios");
	return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

void
gearserial_send( GearSerial *self,
		 const char *str )
{
    int i = 0;
    struct timespec req;
    struct timespec rem;

    req.tv_sec = 0;
    req.tv_nsec = self->sleep_time;

    for( i = 0 ; i < self->many_times ; i++ )
    {
	write( self->fd, &str, strlen(str) );
	nanosleep( &req, &rem );
    }
}

int
gearserial_configure( GearSerial *self,
		      GearSerialConf *conf )
{
    struct termios term;

    /* get current settings */
    if (tcgetattr(self->fd, & term) != 0)
    {
	xerror_error("Nao pude pegar os atributos do termios");
	return EXIT_FAILURE;
    }

    self->sleep_time = conf->sleep_time;
    self->many_times = conf->many_times;

    if( conf->boo_two_stop_bits )
	term.c_cflag |= CS8;
    if( conf->boo_parity_bit )
	term.c_cflag |= PARENB;
    if( conf->boo_parity_odd )
	term.c_cflag |= PARODD;

    /* set terminal attributes */
    if (tcsetattr(self->fd, TCSANOW, &term) != 0)
    {
	xerror_error("Nao pude setar os atributos do termios");
	return EXIT_FAILURE;
    }

    
    return EXIT_SUCCESS;
}

GearSerialConf *
gearserial_conf_new()
{
    GearSerialConf *self = NULL;

    self = XMALLOC( GearSerialConf, 1 );

    self->baud_rade = B9600;
    self->many_times = 1;
    self->sleep_time = 999999999;
    self->boo_two_stop_bits = 0;
    self->boo_parity_bit = 0;
    self->boo_parity_odd = 0;

    return self;
}
